package Pruebas.Victor.MotionDetect.darnok;


/*
 * Author: Konrad Rzeszutek <konrad AT darnok DOT org>
 *
 *
 *
 *
 *    This file is part of Motion Detection toolkit.
 *
 *    Motion Detection toolkit is free software; you can redistribute it and/or modify
 *    it under the terms of the GNU General Public License as published by
 *    the Free Software Foundation; either version 2 of the License, or
 *    (at your option) any later version.
 *
 *    Motion Detection toolkit is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU General Public License for more details.
 *
 *    You should have received a copy of the GNU General Public License
 *    along with Foobar; if not, write to the Free Software
 *    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 */


import javax.media.*;
import javax.media.format.*;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.Date;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.imageio.ImageIO;
import javax.media.util.BufferToImage;
/**
 * Motion detection effect.
 *
 * The engine has two steps.
 * First the input image and the
 * reference image (which is the image from the previous frame) is compared.
 * Whenever a pixel has changed consideribly (determined by the
 * <b>threshold</b> variable) a internal black-white-red image is
 * marked (at the same exact location where the change occured). Therefore in
 * the first step, the internal black-white-red image is has lit up clusters
 * in the space where a change has occured.
 * <p>The next step is to eliminate these clusters that are too small, but still
 * appeared in our black-white-red image. Only the big clusters are left (and
 * are colored red). During this process we keep a track of couunt of the big
 * clusters.
 * If the count is greater than <b>blob_threshold</b> then the input frame is
 * determined to have consideribly motion as to the previous frame.
 *
 * <br><br>Many of the ideas have been taken from
 * <ol>
 *  <li><a href="http://gspy.sourceforge.net/">A Gnome Security Camera</a> by
 *  Lawrance P. Glaister.
 *  <li>Digital Image Processing by Kenneth R. Castleman; ISBN 0-13-211467-4
 *  <li>Computer Graphics Principles and Practice by Foley, van Dam, Feiner,
 *  Hughes; ISBN 0-201-84840-6
 *  <li>Java Media Format Sample Programs (mainly the one dealing with
 *     building an Effect plugin)
 * </ol>
 *
 * <br><br>
 * @version: $Id: MotionDetectionEffect.java,v 1.4 2002/04/28 19:14:11 uid500 Exp $
 * @author: Konrad Rzeszutek <konrad@darnok.org>
 *
 */
public class MotionDetectionEffect3 implements Effect {
    
        private RGBFormat vfIn = null;
        private final static int INITIAL_SQUARE_SIZE = 5;

    public final static Format[] supportedFormat = new Format[] { // 
    new RGBFormat(null, //
            Format.NOT_SPECIFIED, // 
            Format.byteArray, //
            Format.NOT_SPECIFIED, //
            24, //
            3, 2, 1, //
            3, Format.NOT_SPECIFIED, //
            Format.TRUE, //
            Format.NOT_SPECIFIED) //
    };

    private Format inputFormat;

    private Format outputFormat;

    private Format[] inputFormats;

    private Format[] outputFormats;

    private int[] bwPixels;

    private byte[] bwData;

    /**
     * Visual mode is set.
     */
    private boolean visualize = true;

    /**
     * Server mode is set.
     */
    private boolean serverActive = true;

    /**
     * Update requested is set.
     */
    private boolean updateRequested;

    private int avg_ref_intensity;

    private int avg_img_intensity;

    /**
     * The RGBFormat of the inbuffer.
     */
  

    /**
     * Four different thresholds. Set initial values here.
     */
    private int[] threshs = { 20, 30, 40, 50 };

    private int det_thresh = threshs[1];

    /**
     *  The corresponding colours to the four different thresholds.
     */
    private int[] colors = { 0x00FF0000, 0x00FF9900, 0x00FFFF00, 0x00FFFFFF };

    /**
     *  The mean values of all squares in an image.
     */
    private int[] newImageSquares = null;

    /**
     *  The mean values of all squares in an image.
     */
    private int[] oldImageSquares = null;

    /**
     *  The difference of all the mean values of all squares in an image.
     */
    private int[] changedSquares = null;

    /**
     * The number of squares fitted in the image.
     */
    private int numberOfSquaresWide;

    /**
     * The number of squares fitted in the image.
     */
    private int numberOfSquaresHigh;

    /**
     * The number of squares fitted in the image.
     */
    private int numberOfSquares;

    /**
     * The square side, in pixels.
     */
    private int sqSide = INITIAL_SQUARE_SIZE;

    /**
     * The square area, in pixels.
     */
    private int sqArea = 0;

    /**
     * The amount of pixels left when all normal sized squares have been removed.
     */
    private int sqWidthLeftover = 0;

    /**
     * The amount of pixels left when all normal sized squares have been removed.
     */
    private int sqHeightLeftover = 0;

    /**
     * Optional, less processing is needed if some pixels are left out during some of the calculations.
     */
    private int pixelSpace = 0;

    /**
     * Image property.
     */
    private int imageWidth = 0;

    /**
     * Image property.
     */
    private int imageHeight = 0;

    /**
     * Image property.
     */
    private int imageArea = 0;



  /**
   * Optimization. Anything above 0 turns it on. By default its
   * disabled.
   */
    //public int OPTIMIZATION = 0;
    /**
     * Maximum threshold setting. Setting the threshold above this
     * means to get the motion detection to pass the frame you pretty
     * much have to full the whole frame with lots of motions (ie: drop
     * the camera)
     */
    //public int THRESHOLD_MAX = 10000;

    /**
     * By what value you should increment.
     */
    //public int THRESHOLD_INC = 1000;

    /**
     * The initial threshold setting.
     */
    public int THRESHOLD_INIT = 5000;

    

    /**
     * The threshold for determing if the pixel at a certain location has
     * changed consideribly.
     */
    public int threshold = 30;
    /**
     * Our threshold for determinig if the input image has enough motion.
     *
     */
    public int blob_threshold = THRESHOLD_INIT;

    /**
     * Turn debugging on. Slows down the effect but shows how motion
     * detection effect works.
     */
    public boolean debug = false;

    /**
     * Initialize the effect plugin.
     */
    public MotionDetectionEffect3() {

        inputFormats = new Format[] {
            new RGBFormat(null,
                          Format.NOT_SPECIFIED,
                          Format.byteArray,
                          Format.NOT_SPECIFIED,
                          24,
                          3, 2, 1,
                          3, Format.NOT_SPECIFIED,
                          Format.TRUE,
                          Format.NOT_SPECIFIED)
        };

        outputFormats = new Format[] {
            new RGBFormat(null,
                          Format.NOT_SPECIFIED,
                          Format.byteArray,
                          Format.NOT_SPECIFIED,
                          24,
                          3, 2, 1,
                          3, Format.NOT_SPECIFIED,
                          Format.TRUE,
                          Format.NOT_SPECIFIED)
        };

    }

    /**
     * Get the format that we support
     *
     * @return  instanceof RGBFormat
     */
    public Format[] getSupportedInputFormats() {
	return inputFormats;
    }

    /**
     * Get the format that we support
     *
     * @return  instanceof RGBFormat
     */
    public Format [] getSupportedOutputFormats(Format input) {
        if (input == null) {
            return outputFormats;
        }

        if (matches(input, inputFormats) != null) {
            return new Format[] { outputFormats[0].intersects(input) };
        } else {
            return new Format[0];
        }
    }

    /**
     * Set our input format.
     */
    public Format setInputFormat(Format input) {

	inputFormat = input;
	return input;
    }

    /**
     * Set our output format.
     *
     */
    public Format setOutputFormat(Format output) {

        if (output == null || matches(output, outputFormats) == null)
            return null;

        RGBFormat incoming = (RGBFormat) output;

        Dimension size = incoming.getSize();
        int maxDataLength = incoming.getMaxDataLength();
        int lineStride = incoming.getLineStride();
        float frameRate = incoming.getFrameRate();
        int flipped = incoming.getFlipped();
        int endian = incoming.getEndian();

        if (size == null)
            return null;
        if (maxDataLength < size.width * size.height * 3)
            maxDataLength = size.width * size.height * 3;
        if (lineStride < size.width * 3)
            lineStride = size.width * 3;
        if (flipped != Format.FALSE)
            flipped = Format.FALSE;

        outputFormat = outputFormats[0].intersects(new RGBFormat(size,
                                                        maxDataLength,
                                                        null,
                                                        frameRate,
                                                        Format.NOT_SPECIFIED,
                                                        Format.NOT_SPECIFIED,
                                                        Format.NOT_SPECIFIED,
                                                        Format.NOT_SPECIFIED,
                                                        Format.NOT_SPECIFIED,
                                                        lineStride,
                                                        Format.NOT_SPECIFIED,
                                                        Format.NOT_SPECIFIED));

        return outputFormat;
    }

   /**
    */

    public int process(Buffer inBuffer, Buffer outBuffer) {

   int outputDataLength = ((VideoFormat) outputFormat).getMaxDataLength();
        validateByteArraySize(outBuffer, outputDataLength);
        outBuffer.setLength(outputDataLength);
        outBuffer.setFormat(outputFormat);
        outBuffer.setFlags(inBuffer.getFlags());

        byte[] inData = (byte[]) inBuffer.getData();
        byte[] outData = (byte[]) outBuffer.getData();
        int[] sqAvg = null;
        int[] refsqAvg = null;

        vfIn = (RGBFormat) inBuffer.getFormat();
        Dimension sizeIn = vfIn.getSize();

        int pixStrideIn = vfIn.getPixelStride();
        int lineStrideIn = vfIn.getLineStride();

        imageWidth = (vfIn.getLineStride()) / 3; //Divide by 3 since each pixel has 3 colours.
        imageHeight = ((vfIn.getMaxDataLength()) / 3) / imageWidth;
        imageArea = imageWidth * imageHeight;

        int r, g, b = 0; //Red, green and blue values.

        if (oldImageSquares == null) { //For the first frame.
            changeSqSize(INITIAL_SQUARE_SIZE);
            updateRequested = true;
        }

        //Copy all data from the inbuffer to the outbuffer. The purpose is to display the video input on the screen.
        System.arraycopy(inData, 0, outData, 0, outData.length);

        // Simplify the image to black and white, image information shrinks to one third of the original amount. Less processing needed.
        bwPixels = new int[outputDataLength / 3];
        for (int ip = 0; ip < outputDataLength; ip += 3) {
            int bw = 0;
            r = (int) inData[ip] & 0xFF;
            g = (int) inData[ip + 1] & 0xFF;
            b = (int) inData[ip + 2] & 0xFF;
            bw = (int) ((r + b + g) / (double) 3);
            bwPixels[ip / 3] = bw; //Now containing a black and white image. 
        }

        if (updateRequested) {
            updateRequested = false;
            updateSquares();
            return BUFFER_PROCESSED_OK;
        } else {
            updateSquares();
            oldNewChange();
            int c = 0;
            for (int i = 0; i < changedSquares.length; i++) {
                if (changedSquares[i] > det_thresh) {
                    c++;
                }
            }

            if (c > 10 && serverActive) {
                //    try{
                System.out.println("Motion detected (motion at " + c + "areas");
                //      multicast.send("Motion detected");  - Disabled
                //    } catch(IOException e){}
            }

            // If chosen, the detected motion is presented on top of the video input, thus covering the edges of the moving object. 
            if (visualize) {
                for (int i = 1; i <= numberOfSquares; i++) { // For all blobs
                    if ((changedSquares[i - 1] > threshs[0])) { // Critical threshold, if less, then no motion is said to have occured.
                        if (((i % numberOfSquaresWide) != 0) && (numberOfSquares - i) > numberOfSquaresWide) {//Normal square, the other cases is not presented!
                            int begin = ((((i % numberOfSquaresWide) - 1) * sqSide) + ((i / numberOfSquaresWide) * imageWidth * sqSide)) * 3; //Calculate start of square.

                            if (changedSquares[i - 1] > threshs[3]) { //Very strong motion.
                                b = (byte) (colors[3] & 0xFF);
                                g = (byte) ((colors[3] >> 8) & 0xFF);
                                r = (byte) ((colors[3] >> 16) & 0xFF);
                            } else if (changedSquares[i - 1] > threshs[2]) { //Strong motion.
                                b = (byte) (colors[2] & 0xFF);
                                g = (byte) ((colors[2] >> 8) & 0xFF);
                                r = (byte) ((colors[2] >> 16) & 0xFF);
                            } else if (changedSquares[i - 1] > threshs[1]) { //Weak motion.
                                b = (byte) (colors[1] & 0xFF);
                                g = (byte) ((colors[1] >> 8) & 0xFF);
                                r = (byte) ((colors[1] >> 16) & 0xFF);
                            } else { //The Weakest motion detected.
                                b = (byte) (colors[0] & 0xFF);
                                g = (byte) ((colors[0] >> 8) & 0xFF);
                                r = (byte) ((colors[0] >> 16) & 0xFF);
                            }
                            for (int k = begin; k < (begin + (sqSide * imageWidth * 3)); k = k + (imageWidth * 3)) { //Ev <=
                                for (int j = k; j < (k + (sqSide * 3)); j = j + 3) {
                                    try {
                                        outData[j] = (byte) b;
                                        outData[j + 1] = (byte) g;
                                        outData[j + 2] = (byte) r;
                                    } catch (ArrayIndexOutOfBoundsException e) {
                                        System.out.println("Nullpointer: j = " + j + ". Outdata.length = " + outData.length);
                                        System.exit(1);
                                    }
                                }
                            }

                        }
                    }
                }
            }
        }
        System.out.println("Se supone detecta movimiento");
        return BUFFER_PROCESSED_OK;     
    }
     private void changeSqSize(int newSide) {
        sqSide = newSide;
        sqArea = newSide * newSide;
        int wid = (imageWidth / sqSide); //The number of squares wide.
        int hei = (imageHeight / sqSide); //The number of squares high.
        sqWidthLeftover = imageWidth % sqSide;
        sqHeightLeftover = imageHeight % sqSide;
        if (sqWidthLeftover > 0) {
            wid++;
        }
        if (sqHeightLeftover > 0) {
            hei++;
        }

        numberOfSquaresWide = wid;
        numberOfSquaresHigh = hei;
        numberOfSquares = wid * hei;

        newImageSquares = new int[numberOfSquares];
        oldImageSquares = new int[numberOfSquares];
        changedSquares = new int[numberOfSquares];
    }
    
    private void updateSquares() {
        System.arraycopy(newImageSquares, 0, oldImageSquares, 0, newImageSquares.length);
        int sqCount = 0; //Keep track of the current square
        for (int j = 0; j < (imageHeight); j = j + sqSide) { //For all squares
            for (int i = 0; i < (imageWidth); i = i + sqSide) {
                if (i <= (imageWidth - sqSide) && j <= (imageHeight - sqSide)) {
                    newImageSquares[sqCount] = averageInSquare(i, j, sqSide, sqSide); //No edge!
                } else if (i > (imageWidth - sqSide) && j <= (imageHeight - sqSide)) {
                    newImageSquares[sqCount] = averageInSquare(i, j, sqWidthLeftover, sqSide); //Right edge!
                } else if (i <= (imageWidth - sqSide) && j > (imageHeight - sqSide)) {
                    newImageSquares[sqCount] = averageInSquare(i, j, sqSide, sqHeightLeftover); //Bottom edge!
                } else if (i > (imageWidth - sqSide) && j > (imageHeight - sqSide)) {
                    newImageSquares[sqCount] = averageInSquare(i, j, sqWidthLeftover, sqHeightLeftover); //Bottom right edge!
                }
                sqCount++;
            }
        }
    }
private int averageInSquare(int startX, int startY, int sqWidth, int sqHeight) {
        int average = 0;
        for (int i = 0; i < sqHeight; i = i + 1 + pixelSpace) {// For all pixels
            for (int j = 0; j < sqWidth; j = j + 1 + pixelSpace) {
                average += bwPixels[(((startY + i) * imageWidth) + (startX + j))]; //Sum all the pixel values.
            }
        }
        average = average / (sqWidth * sqHeight); //Divide by the number of pixels to get the average value.
        return average;
    }
    
      /**
     * Calculate the difference per square between currently stored frames. 
     */
    private void oldNewChange() {
        for (int i = 0; i <= (numberOfSquares - 1); i++) { //For all squares
            int difference = Math.abs((newImageSquares[i]) - (oldImageSquares[i])); //Compare each square with the corresponding square in the previous frame.
            changedSquares[i] = difference; //Save the difference.
        }
    }
    
    static public void saveImagetoFile(Image img, String fileName) throws IOException {

       int           w = img.getWidth(null);
       int           h = img.getHeight(null);
       BufferedImage bi = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
       Graphics2D    g2 = bi.createGraphics();
       g2.drawImage(img, 0, 0, null);
       g2.dispose();
       String fileType = fileName.substring(fileName.indexOf('.')+1);
       ImageIO.write(bi, fileType, new File(fileName));
     }

    // methods for interface PlugIn
    public String getName() {
        return "Motion Detection Codec";
    }

    public void open() {
    }

    public void close() {
    }

    public void reset() {
    }

    // methods for interface javax.media.Controls
    public Object getControl(String controlType) {
        System.out.println(controlType);
	return null;
    }
    private Control[] controls;


    public Object[] getControls() {
      if (controls == null) {
        controls = new Control[1];
       // controls[0] = new MotionDetectionControl(this);
      }
      return (Object[])controls;
    }


    // Utility methods.
    Format matches(Format in, Format outs[]) {
	for (int i = 0; i < outs.length; i++) {
	    if (in.matches(outs[i]))
		return outs[i];
	}

	return null;
    }


    void sample_down(byte[] inData, byte[] outData, int X, int Y, int width,
    int height, int lineStrideIn, int pixStrideIn) {

          int p1, p2, p3, p4, op,x,y;

      for ( y = 0; y < (height/2); y++) {

        p1 = (y * 2) * lineStrideIn ; // upper left cell
        p2 = p1 + pixStrideIn;                    // upper right cell
        p3 = p1 + lineStrideIn;         // lower left cell
        p4 = p3 + pixStrideIn;                    // lower right cell
        op = lineStrideIn * y + (lineStrideIn*Y) + (X*pixStrideIn);
        for ( int i =0; i< (width /2 );i++) {
/*
          outData[op++] = (byte)((inData[p1++] + inData[p2++] + inData[p3++] + inData[p4++])/4); // blue cells avg
          outData[op++] = (byte)((inData[p1++] + inData[p2++] + inData[p3++] + inData[p4++])/4); // green cells avg
          outData[op++] = (byte)((inData[p1++] + inData[p2++] + inData[p3++] + inData[p4++])/4); // red cells avg
*/
          outData[op++] = (byte)(((int)(inData[p1++] & 0xFF) +
            ((int)inData[p2++] & 0xFF)+ ((int)inData[p3++] & 0xFF) +
            ((int)inData[p4++] & 0xFF))/4); // blue cells avg
          outData[op++] = (byte)(((int)(inData[p1++] & 0xFF) +
            ((int)inData[p2++] & 0xFF)+ ((int)inData[p3++] & 0xFF) +
              ((int)inData[p4++] & 0xFF))/4); // blue cells avg
          outData[op++] = (byte)(((int)(inData[p1++] & 0xFF) +
            ((int)inData[p2++] & 0xFF)+ ((int)inData[p3++] & 0xFF) +
              ((int)inData[p4++] & 0xFF))/4); // blue cells avg
          p1 += 3; p2 += 3; p3+= 3; p4 += 3;
        }

      }
    }

    byte[] validateByteArraySize(Buffer buffer,int newSize) {
        Object objectArray=buffer.getData();
        byte[] typedArray;

        if (objectArray instanceof byte[]) {     // is correct type AND not null
            typedArray=(byte[])objectArray;
            if (typedArray.length >= newSize ) { // is sufficient capacity
                return typedArray;
            }
            byte[] tempArray=new byte[newSize];  // re-alloc array
            System.arraycopy(typedArray,0,tempArray,0,typedArray.length);
            typedArray = tempArray;
        } else {
            typedArray = new byte[newSize];
        }

        buffer.setData(typedArray);
        return typedArray;
    }

}
